﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace UIPrototyper
{
    /// <summary>
    /// Interaction logic for ScreenUC.xaml
    /// </summary>
    public partial class ScreenUC : UserControl, ISelectable
    {
        public event MovedHandler Moved;

        private Screen screen;
        private Canvas canvas;
        Dictionary<ScreenUC, Line> lines;
        Dictionary<Line, TextBlock> blocks;
        Dictionary<Line, Polygon> arrows;
        List<CustomButton> buttonList;

        public Screen MyScreen
        {
            get { return screen; }
        }

        public ScreenUC(string pThickness, Screen pScreen, Canvas pCanvas, MainUI mainUI)
        {
            InitializeComponent();

            this.Margin = MarginToThicknessConverter.ConvertToThickness(pThickness);
            this.Width = 100;
            this.Height = 40;

            this.lines = new Dictionary<ScreenUC, Line>();
            this.blocks = new Dictionary<Line, TextBlock>();
            this.arrows = new Dictionary<Line, Polygon>();
            this.buttonList = new List<CustomButton>();

            this.canvas = pCanvas;
            this.screen = pScreen;
            //this.lblScreenName.Content = pScreen.Name;
            this.DataContext = pScreen;

            this.screen.iChanged += new ChangedHandler(screen_iChanged);
            mainUI.AddedScreenToWorkflow += new AddedScreenToWorkflowHandler(mainUI_AddedScreenToWorkflow);
            mainUI.RemovedScreenFromWorkflow += new RemovedScreenFromWorkflowHandler(mainUI_RemovedScreenFromWorkflow);

            foreach (FrameworkElement c in screen.ControlList)
            {
                if (c.GetType() == typeof(CustomButton))
                {
                    buttonList.Add((CustomButton)c);
                }
            }

            foreach (CustomButton btn in buttonList)
            {
                AddLineWithButton(btn);
            }

            AddScreenToolTip();
        }

        public ScreenUC(string pThickness, Screen pScreen, Canvas pCanvas, MainUI mainUI, double pWidth, double pHeight)
        {
            InitializeComponent();

            this.Margin = MarginToThicknessConverter.ConvertToThickness(pThickness);
            this.Width = pWidth;
            this.Height = pHeight;

            this.lines = new Dictionary<ScreenUC, Line>();
            this.blocks = new Dictionary<Line, TextBlock>();
            this.arrows = new Dictionary<Line, Polygon>();
            this.buttonList = new List<CustomButton>();

            this.canvas = pCanvas;
            this.screen = pScreen;
            //this.lblScreenName.Content = pScreen.Name;
            this.DataContext = pScreen;

            this.screen.iChanged += new ChangedHandler(screen_iChanged);
            mainUI.AddedScreenToWorkflow += new AddedScreenToWorkflowHandler(mainUI_AddedScreenToWorkflow);
            mainUI.RemovedScreenFromWorkflow += new RemovedScreenFromWorkflowHandler(mainUI_RemovedScreenFromWorkflow);

            foreach (FrameworkElement c in screen.ControlList)
            {
                if (c.GetType() == typeof(CustomButton))
                {
                    buttonList.Add((CustomButton)c);
                }
            }

            foreach (CustomButton btn in buttonList)
            {
                AddLineWithButton(btn);
            }

            AddScreenToolTip();
        }

        void screen_iChanged(IChanged changedObject)
        {
            this.screen = (Screen)changedObject;

            //this.lblScreenName.Content = screen.Name;

            buttonList.Clear();

            foreach (Line l in lines.Values)
            {
                this.canvas.Children.Remove(l);
            }

            lines.Clear();

            foreach (FrameworkElement c in ((Screen)changedObject).ControlList)
            {
                if (c.GetType() == typeof(CustomButton))
                {
                    AddLineWithButton((CustomButton)c);
                    buttonList.Add((CustomButton)c);
                }
            }

            AddScreenToolTip();
        }

        void mainUI_AddedScreenToWorkflow(ScreenUC pScreen)
        {
            foreach (CustomButton btn in buttonList)
            {
                if (((ButtonProperties)btn.Properties).ScreenID == pScreen.MyScreen.ID)
                {
                    Line l = new Line();

                    l.X1 = this.Margin.Left + (this.Width / 2);
                    l.Y1 = this.Margin.Top + (this.Height / 2);

                    l.X2 = pScreen.Margin.Left + (pScreen.Width / 2);
                    l.Y2 = pScreen.Margin.Top + (pScreen.Height / 2);

                    l.StrokeThickness = 1;
                    l.Stroke = Brushes.White;

                    AddLineToolTip(l, btn, pScreen);

                    this.lines.Add(pScreen, l);
                    pScreen.Moved += new MovedHandler(pScreen_Moved);
                    canvas.Children.Insert(0, l);
                    canvas.Children.Insert(0, blocks[l]);
                    canvas.Children.Insert(0, arrows[l]);
                }
            }
        }

        void mainUI_RemovedScreenFromWorkflow(ScreenUC pScreen)
        {
            Line l;

            if (lines.TryGetValue(pScreen, out l))
            {
                canvas.Children.Remove(l);
                lines.Remove(pScreen);
                blocks.Remove(l);
            }
        }

        void pScreen_Moved(ScreenUC pScreenUC)
        {
            Line l;

            if (lines.TryGetValue(pScreenUC, out l))
            {

                l.X1 = this.Margin.Left + (this.Width / 2);
                l.Y1 = this.Margin.Top + (this.Height / 2);

                l.X2 = pScreenUC.Margin.Left + (pScreenUC.Width / 2);
                l.Y2 = pScreenUC.Margin.Top + (pScreenUC.Height / 2);

                TextBlock t;

                if (blocks.TryGetValue(l, out t))
                {
                    t.Margin = new Thickness(GetPosition(l.X1, l.X2), GetPosition(l.Y1, l.Y2), 0, 0);
                    UpdateArrow(l, pScreenUC);
                }
            }
        }

        public void Move(Point mousePosition, ref Point startPosition)
        {
            double leftMarginDifference = CalculateMarginDifference(this.Width, mousePosition.X, startPosition.X);
            double topMarginDifference = CalculateMarginDifference(this.Height, mousePosition.Y, startPosition.Y);
            double widthDifference = mousePosition.X - startPosition.X;
            double heightDifference = mousePosition.Y - startPosition.Y;

            switch (this.Operation)
            {
                case Operations.Move:
                    this.SetMargin(new Thickness(this.Margin.Left + leftMarginDifference,
                        this.Margin.Top + topMarginDifference, 0, 0));
                    break;

                case Operations.ResizeDTopLeft:
                    this.SetMargin(new Thickness(this.Margin.Left + leftMarginDifference,
                        this.Margin.Top + topMarginDifference, 0, 0));
                    this.SetWidth(this.Width - widthDifference);
                    this.SetHeight(this.Height - heightDifference);
                    break;

                case Operations.ResizeDTopRight:
                    this.SetMargin(new Thickness(this.Margin.Left, this.Margin.Top + topMarginDifference, 0, 0));
                    this.SetWidth(this.Width + widthDifference);
                    this.SetHeight(this.Height - heightDifference);
                    break;

                case Operations.ResizeDBottomLeft:
                    this.SetMargin(new Thickness(this.Margin.Left + leftMarginDifference,
                        this.Margin.Top, 0, 0));
                    this.SetWidth(this.Width - widthDifference);
                    this.SetHeight(this.Height + heightDifference);
                    break;

                case Operations.ResizeDBottomRight:
                    this.SetWidth(this.Width + widthDifference);
                    this.SetHeight(this.Height + heightDifference);
                    break;

                case Operations.ResizeVTop:
                    this.SetMargin(new Thickness(this.Margin.Left,
                        this.Margin.Top + topMarginDifference, 0, 0));
                    this.SetHeight(this.Height - heightDifference);
                    break;

                case Operations.ResizeHLeft:
                    this.SetMargin(new Thickness(this.Margin.Left + leftMarginDifference,
                        this.Margin.Top, 0, 0));
                    this.SetWidth(this.Width - widthDifference);
                    break;

                case Operations.ResizeHRight:
                    this.SetWidth(this.Width + widthDifference);
                    break;

                case Operations.ResizeVBottom:
                    this.SetHeight(this.Height + heightDifference);
                    break;
            }

            if (this.Height > 10 && this.Width > 10)
            { startPosition = mousePosition; }
            else if (this.Height > 10 && this.Width == 10)
            { startPosition.Y = mousePosition.Y; }
            else if (this.Width > 10 && this.Height == 10)
            { startPosition.X = mousePosition.X; }

            for (int i = 0; i < lines.Keys.Count; i++)
            {
                ScreenUC sc = (ScreenUC)lines.Keys.ElementAt(i);

                Line l = lines[sc];

                l.X1 = this.Margin.Left + (this.Width / 2);
                l.Y1 = this.Margin.Top + (this.Height / 2);

                l.X2 = sc.Margin.Left + (sc.Width / 2);
                l.Y2 = sc.Margin.Top + (sc.Height / 2);

                TextBlock t;

                if (blocks.TryGetValue(l, out t))
                {
                    t.Margin = new Thickness(GetPosition(l.X1, l.X2), GetPosition(l.Y1, l.Y2), 0, 0);
                    UpdateArrow(l, sc);
                }
            }

            if (Moved != null)
            {
                Moved(this);
            }
        }

        private void AddLineWithButton(CustomButton pButton)
        {
            List<Line> newLines = new List<Line>();

            foreach (FrameworkElement sc in this.canvas.Children)
            {
                if (sc.GetType() == typeof(ScreenUC))
                {
                    if (((ButtonProperties)pButton.Properties).ScreenID == ((ScreenUC)sc).MyScreen.ID)
                    {
                        try
                        {
                            Line l = new Line();

                            l.X1 = this.Margin.Left + (this.Width / 2);
                            l.Y1 = this.Margin.Top + (this.Height / 2);

                            l.X2 = sc.Margin.Left + (sc.Width / 2);
                            l.Y2 = sc.Margin.Top + (sc.Height / 2);

                            l.StrokeThickness = 1;
                            l.Stroke = Brushes.White;

                            AddLineToolTip(l, pButton, (ScreenUC)sc);

                            this.lines.Add((ScreenUC)sc, l);
                            newLines.Add(l);
                            ((ScreenUC)sc).Moved += new MovedHandler(pScreen_Moved);
                        }
                        catch { }
                    }
                }
            }

            foreach (Line l in newLines)
            {
                canvas.Children.Insert(0, l);
                canvas.Children.Insert(0, blocks[l]);
                canvas.Children.Insert(0, arrows[l]);
            }
        }

        private void RemoveLineWithButton(CustomButton pButton)
        {
            List<ScreenUC> linesToRemove = new List<ScreenUC>();

            foreach (FrameworkElement sc in this.canvas.Children)
            {
                if (sc.GetType() == typeof(ScreenUC))
                {
                    if (((ButtonProperties)pButton.Properties).ScreenID == ((ScreenUC)sc).MyScreen.ID)
                    {
                        try
                        {
                            linesToRemove.Add((ScreenUC)sc);
                        }
                        catch { }
                    }
                }
            }

            foreach (ScreenUC sc in linesToRemove)
            {
                try
                {
                    this.canvas.Children.Remove(this.lines[(ScreenUC)sc]);
                    lines.Remove(sc);
                }
                catch { }
            }
        }

        private void AddScreenToolTip()
        {
            ToolTip tip = new ToolTip();
            TextBlock txt = new TextBlock();

            txt.Inlines.Add("Nome: " + this.screen.Name);
            txt.Inlines.Add("\nID: " + this.screen.ID);

            if (buttonList.Count > 0)
            {
                txt.Inlines.Add("\nBotões:");

                foreach (CustomButton cb in buttonList)
                {
                    Screen target;

                    txt.Inlines.Add("\n" + ((ButtonProperties)cb.Properties).Texto + " -> ");

                    if (ProjectSingleton.Project.Screens.TryGetValue(((ButtonProperties)cb.Properties).ScreenID, out target))
                    {
                        txt.Inlines.Add(target.Name);
                    }
                    else
                    {
                        txt.Inlines.Add("Não especificado!");
                    }
                }
            }

            tip.Content = txt;
            this.ToolTip = tip;
        }

        private void AddLineToolTip(Line pLine, CustomButton pCustomButton, ScreenUC pTarget)
        {
            ToolTip tip = new ToolTip();
            TextBlock txt = new TextBlock();

            txt.Inlines.Add("Botão: " + ((ButtonProperties)pCustomButton.Properties).Texto);
            txt.Inlines.Add("\nOrigem: " + this.screen.Name);
            txt.Inlines.Add("\nDestino: ");

            Screen target;

            if (ProjectSingleton.Project.Screens.TryGetValue(((ButtonProperties)pCustomButton.Properties).ScreenID, out target))
            {
                txt.Inlines.Add(target.Name);
            }
            else
            {
                txt.Inlines.Add("Não especificado!");
            }

            tip.Content = txt;
            pLine.ToolTip = tip;

            TextBlock ttb = new TextBlock();

            ttb.Inlines.Add("Botão: " + ((ButtonProperties)pCustomButton.Properties).Texto);
            ttb.Inlines.Add("\nDestino: ");

            if (ProjectSingleton.Project.Screens.TryGetValue(((ButtonProperties)pCustomButton.Properties).ScreenID, out target))
            {
                ttb.Inlines.Add(target.Name);
            }
            else
            {
                ttb.Inlines.Add("Não especificado!");
            }
            ttb.Foreground = Brushes.White;
            ttb.Margin = new Thickness(GetPosition(pLine.X1, pLine.X2), GetPosition(pLine.Y1, pLine.Y2), 0, 0);

            CreateArrow(pLine, pTarget);

            this.blocks.Add(pLine, ttb);
        }

        private double Cateto(double a, double b)
        {
            return ((a > b ? a : b) - (a < b ? a : b));
        }

        private double GetPosition(double a, double b)
        {
            return ((((a > b ? a : b) - (a < b ? a : b)) / 2) + (a < b ? a : b));
        }

        private void CreateArrow(Line pLine, ScreenUC pTarget)
        {
            Polygon p = new Polygon();
            double angle, pt1, pt2, pt3, pt4;

            PointCollection points = new PointCollection();
            points.Add(new Point(0, 0));
            points.Add(new Point(10, 1));
            points.Add(new Point(1, 9));

            p.Points = points;

            Point point = new Point();

            if (!LineLineIntersect(pLine, pTarget, out point))
            {
                point.X = pLine.X1;
                point.Y = pLine.Y1;
            }

            p.Margin = new Thickness(point.X, point.Y, 0, 0);

            p.Fill = Brushes.White;
            p.Width = 10;
            p.Height = 10;
            p.Stretch = Stretch.Fill;
            p.Stroke = Brushes.White;
            p.StrokeThickness = 2;

            pt1 = pLine.X2 - pLine.X1;
            pt2 = pLine.Y2 - pLine.Y1;
            pt3 = pTarget.Width;
            pt4 = 0;

            angle = (pt1 * pt3) / ((Math.Sqrt(pt1 * pt1 + pt2 * pt2)) * (Math.Sqrt(pt3 * pt3)));

            p.RenderTransform = new RotateTransform(((Math.Acos(angle) * 180 / Math.PI) + 135));

            if (pLine.Y2 < pLine.Y1)
            {
                ((RotateTransform)p.RenderTransform).Angle = ((((RotateTransform)p.RenderTransform).Angle - 135) * -1) + 135;
            }

            p.RenderTransformOrigin = new Point(0, 0);

            arrows.Add(pLine, p);

            pLine.X2 = point.X;
            pLine.Y2 = point.Y;
        }

        private void UpdateArrow(Line pLine, ScreenUC pTarget)
        {
            double angle, pt1, pt2, pt3, pt4;
            Polygon p = arrows[pLine];

            Point point = new Point();

            if (!LineLineIntersect(pLine, pTarget, out point))
            {
                point.X = pLine.X1;
                point.Y = pLine.Y1;
            }

            p.Margin = new Thickness(point.X, point.Y, 0, 0);

            pt1 = pLine.X2 - pLine.X1;
            pt2 = pLine.Y2 - pLine.Y1;
            pt3 = pTarget.Width;
            pt4 = 0;

            angle = (pt1 * pt3) / ((Math.Sqrt(pt1 * pt1 + pt2 * pt2)) * (Math.Sqrt(pt3 * pt3)));

            p.RenderTransform = new RotateTransform(((Math.Acos(angle) * 180 / Math.PI) + 135));

            if (pLine.Y2 < pLine.Y1)
            {
                ((RotateTransform)p.RenderTransform).Angle = ((((RotateTransform)p.RenderTransform).Angle - 135) * -1) + 135;
            }

            p.RenderTransformOrigin = new Point(0, 0);

            pLine.X2 = point.X;
            pLine.Y2 = point.Y;
        }

        #region teste
        Operations operation = Operations.None;

        public Operations Operation
        {
            get { return operation; }
            set { operation = value; }
        }

        private void rectMain_MouseDown(object sender, MouseButtonEventArgs e)
        {
            rectMain.Cursor = Cursors.ScrollAll;
            operation = Operations.Move;
        }

        private void rectMain_MouseUp(object sender, MouseButtonEventArgs e)
        {
            rectMain.Cursor = Cursors.Hand;
            operation = Operations.None;
        }

        private void rectUpperLeft_MouseDown(object sender, MouseButtonEventArgs e)
        {
            operation = Operations.ResizeDTopLeft;
        }

        private void rectUpperRight_MouseDown(object sender, MouseButtonEventArgs e)
        {
            operation = Operations.ResizeDTopRight;
        }

        private void rectLowerLeft_MouseDown(object sender, MouseButtonEventArgs e)
        {
            operation = Operations.ResizeDBottomLeft;
        }

        private void rectLowerRight_MouseDown(object sender, MouseButtonEventArgs e)
        {
            operation = Operations.ResizeDBottomRight;
        }

        private void rectTop_MouseDown(object sender, MouseButtonEventArgs e)
        {
            operation = Operations.ResizeVTop;
        }

        private void rectLeft_MouseDown(object sender, MouseButtonEventArgs e)
        {
            operation = Operations.ResizeHLeft;
        }

        private void rectRight_MouseDown(object sender, MouseButtonEventArgs e)
        {
            operation = Operations.ResizeHRight;
        }

        private void rectBottom_MouseDown(object sender, MouseButtonEventArgs e)
        {
            operation = Operations.ResizeVBottom;
        }

        private new void MouseUp(object sender, MouseButtonEventArgs e)
        {
            operation = Operations.None;
        }

        public void SetMargin(Thickness pMargin)
        {
            this.Margin = pMargin;
        }

        public void SetWidth(double pWidth)
        {
            pWidth = pWidth < 10 ? 10 : pWidth;
            this.Width = pWidth;
        }

        public void SetHeight(double pHeight)
        {
            pHeight = pHeight < 10 ? 10 : pHeight;
            this.Height = pHeight;
        }

        private void MenuItem_Click(object sender, RoutedEventArgs e)
        {

        }

        private double CalculateMarginDifference(double pSize, double pMousePosition, double pStartPosition)
        {
            double marginDifference = pMousePosition - pStartPosition;

            if ((pSize - marginDifference) < 10)
                marginDifference -= (10 - (pSize - marginDifference));

            return marginDifference;
        }

        #region ISelectable Members

        public string Description
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public Property Properties
        {
            get { throw new NotImplementedException(); }
        }

        #endregion

        #region IChanged Members

        public event ChangedHandler iChanged;

        public void LaunchChangedEvent(IChanged pChangedObject)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region ICopy Members

        public ISelectable Copy()
        {
            throw new NotImplementedException();
        }

        #endregion
        #endregion

        #region teste colisao

        private bool LineLineIntersect(Line l, ScreenUC sc, out Point point)
        {
            Line screenLine1 = new Line(), screenLine2 = new Line(), screenLine3 = new Line(), screenLine4 = new Line();

            screenLine1.X1 = sc.Margin.Left;
            screenLine1.Y1 = sc.Margin.Top;
            screenLine1.X2 = sc.Margin.Left + sc.Width;
            screenLine1.Y2 = sc.Margin.Top;

            screenLine2.X1 = sc.Margin.Left + sc.Width;
            screenLine2.Y1 = sc.Margin.Top;
            screenLine2.X2 = sc.Margin.Left + sc.Width;
            screenLine2.Y2 = sc.Margin.Top + sc.Height;

            screenLine3.X1 = sc.Margin.Left;
            screenLine3.Y1 = sc.Margin.Top + sc.Height;
            screenLine3.X2 = sc.Margin.Left + sc.Width;
            screenLine3.Y2 = sc.Margin.Top + sc.Height;

            screenLine4.X1 = sc.Margin.Left;
            screenLine4.Y1 = sc.Margin.Top;
            screenLine4.X2 = sc.Margin.Left;
            screenLine4.Y2 = sc.Margin.Top + sc.Height;

            if (LineLineIntersect(l, screenLine1, out point))
                return true;

            if (LineLineIntersect(l, screenLine2, out point))
                return true;

            if (LineLineIntersect(l, screenLine3, out point))
                return true;

            if (LineLineIntersect(l, screenLine4, out point))
                return true;

            return false;
        }

        private bool LineLineIntersect(Line l1, Line l2, out Point point)
        {
            Point a, b, c, d;

            a = new Point();
            b = new Point();
            c = new Point();
            d = new Point();

            a.X = l1.X1;
            a.Y = l1.Y1;

            b.X = l1.X2;
            b.Y = l1.Y2;

            c.X = l2.X1;
            c.Y = l2.Y1;

            d.X = l2.X2;
            d.Y = l2.Y2;

            return LineLineIntersect(a, b, c, d, out point);
        }

        private bool LineLineIntersect(Point a, Point b, Point c,
            Point d, out Point point)
        {
            point = new Point();

            double r, s;
            double denominator = (b.X - a.X) * (d.Y - c.Y) - (b.Y - a.Y) * (d.X - c.X);

            // If the denominator in above is zero, AB & CD are colinear
            if (denominator == 0)
            {
                return false;
            }

            double numeratorR = (a.Y - c.Y) * (d.X - c.X) - (a.X - c.X) * (d.Y - c.Y);
            r = numeratorR / denominator;

            double numeratorS = (a.Y - c.Y) * (b.X - a.X) - (a.X - c.X) * (b.Y - a.Y);
            s = numeratorS / denominator;

            // non-intersecting
            if (r < 0 || r > 1 || s < 0 || s > 1)
            {
                return false;
            }

            // find intersection point
            point.X = (float)(a.X + (r * (b.X - a.X)));
            point.Y = (float)(a.Y + (r * (b.Y - a.Y)));

            return true;
        }
        #endregion
    }
}
